﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Diagnostics;

namespace ROrganizer.Client
{
    public class HashFacility
    {
        public HashEntry ComputeHash(string file)
        {
            var finfo = new FileInfo(file);

            using (var computer = MD5.Create())
            using (var stream = finfo.OpenRead())
            using (var subset = new SubsetStream { Inner = stream, Size = 10 * 1024 * 1024 })
            {
                var hash = computer.ComputeHash(subset);
                var timestamp = finfo.LastWriteTimeUtc.ToUniversalTime();

                return new HashEntry
                {
                    File = file.ToLower(),
                    FileTimestamp = timestamp,
                    Info = new HashInfo
                    {
                        Hash = HashFacility.HashToString(hash),
                        Link = String.Empty
                    }
                };
            }
        }

        public string ComputeHash(Stream stream)
        {
            using (var computer = MD5.Create())
            using (var subset = new SubsetStream { Inner = stream, Size = 10 * 1024 * 1024 })
            {
                var hash = computer.ComputeHash(subset);
                return HashFacility.HashToString(hash);
            }
        }

        private static string HashToString(byte[] hash)
        {
            var output = new StringBuilder();

            foreach (var value in hash)
                output.Append(value.ToString("x2"));

            return output.ToString();
        }

        private class SubsetStream : Stream
        {
            public long Size { get; set; }
            public Stream Inner { get; set; }

            public override bool CanRead
            {
                get { return this.Inner.CanRead; }
            }

            public override bool CanSeek
            {
                get { return this.Inner.CanSeek; }
            }

            public override bool CanWrite
            {
                get { return this.Inner.CanWrite; }
            }

            public override void Flush()
            {
                this.Inner.Flush();
            }

            public override long Length
            {
                get { return Math.Min(this.Inner.Length, this.Size); }
            }

            public override long Position
            {
                get { return this.Inner.Position; }
                set { this.Inner.Position = value; }
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                return this.Inner.Read(buffer, offset, (int)Math.Min(count, this.Size - this.Position));
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                switch (origin)
                {
                    case SeekOrigin.Begin: return this.Inner.Seek(Math.Min(offset, this.Size), origin);
                    case SeekOrigin.Current: return this.Inner.Seek(Math.Min(offset, this.Size - this.Position), origin);
                    default: throw new NotSupportedException();
                }
            }

            public override void SetLength(long value)
            {
                this.Inner.SetLength(Math.Min(value, this.Size));
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                this.Inner.Write(buffer, offset, (int)Math.Min(count, this.Size - this.Position));
            }
        }
    }
}
